프록시와 프록시 패턴, 데코레이터 패턴

단순히 한 가지 기능을 분리한다면 전형적인 전략패턴을 사용하면 된다. 하지만 전략 패턴으로는 구체적인 구현 코드는 제거했을지 몰라도 위임을 통해 기능을 사용하는 코드는 핵심코드와 함께 남아 있다.

이와 같은 부가 기능 전부를 핵심 코드가 담긴 클래스에서 독립시킬 수 있다.
부가 기능을 담은 클래스는 중요한 특징이 있는데, 원래 핵심 기능을 가진 클래스로 위임해줘야 한다.
즉, 부가기능이 핵심기능을 사용하는 구조가 되어야 한다.

이렇게 만든 구조를, 클라이언트가 핵심기능을 가진 클래스를 직접 사용하는 것이 아닌 부가기능을 구현한 클래스를 사용하게 하여 핵심기능을 사용하게 만든다.

이렇게 마치 자신이 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받아주는 것을 프록시 라고 부르고,
프록시를 통해 최종적으로 요청을 위임받아 처리하는 실제 오브젝트를 타깃 이라고 한다.

프록시의 특징은 1. 타깃과 같은 인터페이스를 구현했다는 것과 2. 프록시가 타깃을 제어할 수 있는 위치에 있다는 것이다.

프록시는 목적에 따라 두 가지로 구분할 수 있다. 1. 클라이언트가 타깃에 접근하는 방법을 제어하기 위해 2. 타깃에 부가적인 기능을 부여해주기 위해

프록시를 두고 사용한다는 점은 동일 하지만, 목적에 따라 디자인 패턴에서는 다른 패턴으로 구분한다.

데코레이터 패턴

데코레이터 패턴은 타깃에 부가적인 기능을 런타임 시 다이나믹하게 부여해주기 위해 프록시를 사용하는 패턴을 말한다.
데코레이터라는 이름은 실제 내용물은 동일하지만 부가적인 효과를 부여해줄 수 있는 것이 제품은 같은데 다른 포장지로 감싸는 것처럼 보이기 때문이다.

특징은 각 데코레이터가 위임하는 대상에도 인터페이스를 접근하기 때문에 자신이 최종 타깃으로 위임하는지, 아니면 다음 단계의 데코레이터 프록시로 위임하는지 알지 못한다.
그래서 데코레이터의 다음 위임 대상은 인터페이스로 선언하고 생성자나 수정자 메소드를 통해 위임 대상을 외부에서 런타임 시에 주입받을 수 있도록 만들어야 한다.

이는 스프링의 DI를 이용하면 아주 편리한데, 데코레이터 빈의 프로퍼티로 같은 인터페이스를 구현한 다른 데코레이터 또는 타깃 빈을 설정하면 된다.

프록시 패턴

프록시 vs 프록시 패턴

프록시는 클라이언트와 사용 대상 사이에 대리 역할을 맡은 오브젝트를 두는 방법을 총칭했다면,
프록시 패턴은 프록시를 사용하는 방법 중에서 타깃에 대한 접근 방법을 제어하려는 목적을 가진 경우를 가르킨다.

프록시의 타깃의 기능을 확장하거나 추가하는 것이 아니라, 클라이언트가 타깃에 접근하는 방식을 변경해준다.

타깃 오브젝트를 생성하기가 복잡하거나 당장 필요하지 않은 경우에는 꼭 필요한 시점까지 오브젝트를 생성하지 않는 것이 좋다. 그런데 타깃 오브젝트에 대한 레퍼런스가 피리 필요할 때, 프록시 패턴을 적용하여 실제 타깃 오브젝트를 넘기는 것이 아닌 프록시를 넘겨주는 것이다.
그리고 프록시의 메소드를 통해 타깃을 사용하려고 시도하면, 그때 프록시가 타깃 오브젝트를 생성하고 요청을 위임해주는 식이다.

이와 같이 타깃의 기능 자체에는 관여하지 않으면서 접근하는 방법을 제어해주는 프록시를 이용하는 것이 프록시 패턴이다.

구조적으로 보면 프록시와 데코레이터는 유사하지만, 프록시는 자신이 만들거나 접근할 타깃 클래스의 정보를 알고 있는 경우가 많다.

다이나믹 프록시

개발자들은 프록시 만드는 것을 귀찮아 한다. 그래서 자바에서는 쉽게 만들 수 있도록 하는 클래스들을 지원한다.

프록시의 구성과 프록시 작성의 문제점

  1. 타깃의 인터페이스를 구현하고 위임하는 코드를 작성하기가 번거롭다.
  2. 부가기능 코드가 중복될 가능성이 많아진다.

1번을 해결하기 위해 JDK의 다이나믹 프록시가 있다.

리플렉션

다이나믹 프록시는 리플렉션 기능을 이용해서 프록시를 만들어준다.
리플렉션은 자바의코드 자체를 추상화해서 접근하도록 만든 것이다

```java public class ReflectionTest { @Test public void invokeMethod() throws Exception { String name = "Spring";

     // length()
     assertThat(name.length(), is(6));

     Method lengthMethod = String.class.getMethod("length");
     assertThat((Integer)lengthMethod.invoke(name), is(6));

     // charAt()
     assertThat(name.charAt(0), is('S'));

     Method charAtMethod = String.class.getMethod("charAt", int.class);
     assertThat((Character)charAtMethod.invoke(name,0), is('S'));
 }

}

```

### 프록시 클래스

다이나믹 프록시를 이용한 프록시를 만들어보자.

### 다이내믹 프록시 적용

다이내믹 프록시는 프록시 팩토리에 의해 런타임 시 다이내믹하게 만들어지는 오브젝트이다.

다이나믹 프록시 오브젝트의 타입 = 타깃의 인터페이스

프록시 팩토리에게 인터페이스 정보만 제공해주면 해당 인터페이스를 구현한 클래스의 오브젝트를 자동으로 만들어준다.

부가기능 제공 코드는 직접 작성해야 하는데, 이는 프록시 오브젝트와 독립적으로 InvocationHandler를 구현한 오브젝트에 담는다.

```java

public interface InvocationHandler{ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }


 다이내믹 프록시 오브젝트는 클라이언트의 모든 요청을 리플렉션 정보로 변환해서 `InvocationHandler` 구현 오브젝트의 `invoke()` 메소드로 넘기는 것이다.  

 `InvocationHandler` 구현 오브젝트가 타깃 오브젝트 레퍼런스를 갖고 있다면 리플렉션을 이용해 간단히 위임 코드를 만들어 낼 수 있다.  


 ### 다이내믹 프록시의 확장 

 구현한 `UppercaseHandler`의 모든 메소드의 리턴 타입이 스트링으로 가정되어 있다. 
 스트링 외의 리턴 타입을 갖는 메소드를 추가 시키기 위해 타깃 오브젝트의 메소드 호출 후 리턴 타입을 확인해서 스트링인 경우만 대문자로 바꿔주고 나머지는 그대로 넘겨주는 방식으로 수정하자.  

 또한 타깃 종류에 상관없이도 적용이 가능 한 것이기 때문에, 리플렉션의 `Method`인터페이스를 이용해 타깃의 메소드를 호출하는 것이니까 `Hello`타입의 타깃으로 제한할 필요도 없다.  


 ```java

public class UppercaseHandler implements InvocationHandler {
    Object target;

    // Issue : 왜 private이 안되지?
    public UppercaseHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret = method.invoke(target, args);
        if(ret instanceof String && method.getName().startsWith("say")){
            return ((String)ret).toUpperCase();
        } else {
            return ret;
        }
    }
}

## 다이내믹 프록시를 이용한 트랜잭션 부가기능

UserServiceTx를 다이내믹 프록시 방식으로 변경해보자.

현재 1. 서비스 인터페이스의 메소드를 모두 구현해야 하고 2. 트랜잭션이 필요한 메소드마다 트랜잭션 처리 코드가 중복

따라서 트랜잭션 부가기능을 제공하는 다이나믹 프록시를 만들어 적용해보자.

### 트랜잭션 InvocationHandler

요청을 위임할 타깃을 DI로 제공받도록 한다. 따라서 UserServiceImpl 외에 트랜잭션 적용이 필요한 어떤 타깃 오브젝트에도 적용할 수 있다.

## 다이내믹 프록시를 위한 팩토리 빈

이제 TransactionHandler와 다이내믹 프록시를 스프링의 DI를 통해 사용할 수 있도록 만들어보자.

문제는 DI의 대상이 되는 다이내믹 프록시 오브젝트는 일반적인 스프링의 빈으로 등록할 방법이 없다.

스프링은 내부적으로 리플렉션 API를 이용해서 빈 정의에 나오는 클래스 이름을 가지고 빈 오브젝트를 생성한다. 하지만 다이내믹 프록시 오브젝트는 이런 식으로 프록시 오브젝트가 생성되지 않는다. 클래스 자체도 내부적으로 다이내믹하게 새로 정의해서 사용하기 때문에 다이내믹 오브젝트 클래스가 어떤 것인지 알 수도 없다.

### 팩토리 빈

사실 스프링은 클래스 정보를 가지고 디폴트 생성자를 통해 오브젝트를 만드는 방법 외에도 빈을 만들 수 있는 여러 가지 방법을 제공한다.

대표적인 방법이 팩토리 빈을 이용한 빈 생성 방법이다.

팩토리빈 ? 스프링을 대신해서 오브젝트의 생성로직을 담당하도록 만들어진 특별한 빈 간단히, 스프링의 FactoryBean이라는 인터페이스를 구현한 클래스를 스프링의 빈으로 등록하면 팩토리 빈으로 동작한다.

사실 스프링은 private 생성자를 가진 클래스도 빈으로 등록해주면 리플렉션을 이용해 오브젝트를 만들어준다. (리플렉션은 private으로 선언된 접근 규약을 위반할 수 있는 기능이 있음)

하지만 생성자를 private으로 만들었다는 것은 스태틱 메소드를 통해 오브젝트가 만들어져야 하는 중요한 이유가 있기 때문이므로 이를 무시하고 오브젝트를 강제로 생성하면 위험하다. 그러므로 일반적으로 private 생성자를 가진 클래스를 빈으로 등록하는 일은 권장되지 않는다.

팩토리 빈은 전형적인 팩토리 메소드를 가진 오브젝트다. 스프링은 FactoryBean인터페이스를 구현한 클래스가 빈의 클래스로 정의되면, 팩토리 빈 클래스의 오브젝트 getObject()메소드를 이용해 오브젝트를 가져오고 이를 빈 오브젝트로 사용한다.

### 팩토리 빈의 설정방법

```java

 <bean id="message" class="spring.toby1.learningtest.factorybean.MessageFactoryBean">
     <property name="text" value="Factory Bean"/>
 </bean>

```

MessageFactoryBeangetObject() 메소드가 생성해주는 오브젝트가 message빈의 오브젝트가 된다.

결국, FactoryBean 인터페이스를 구현한 클래스를 스프링 빈으로 만들어두면 getObject()라는 메소드가 생성해주는 오브젝트가 실제 빈의 오브젝트로 대치된다.

다이내믹 프록시를 만들어주는 팩토리 빈

ProxynewProxyInstance() 메소드를 통해서만 생성이 가능한 다이내믹 프록시 오브젝트는 일반적인 방법으로는 스프링의 빈으로 등록할 수 없다. 대신 팩토리 빈을 사용하면 다이내믹 프록시 오브젝트를 스프링의 빈으로 만들어 줄 수가 있다.
팩토리 빈의 getObejct() 메소드에 다이내믹 프록시 오브젝트를 만들어주는 코드를 넣으면 되기 때문이다.

트랜잭션 프록시 팩토리 빈 테스트

TxProxyFactoryBean의 트랜잭션을 지원하는 프록시를 바르게 만들어주는지를 확인하는 게 목적이므로 빈으로 등록된 TxProxyFactoryBean을 직접 가져와서 프록시를 만들어보면 된다.

프록시 팩토리 빈 방식의 장점과 한계

다이내믹 프록시를 생성해주는 팩토리 빈을 사용하는 방법은 한번 부가기능을 가진 프록시를 생성하는 팩토리 빈을 만들어두면 타깃의 타입에 상관없이 재사용할 수 있다.

프록시 팩토리 빈의 재사용

타깃 오브젝트에 맞는 프로퍼티 정보를 설정해서 빈으로 등록해주기만 하면 되기 때문에, 코드 수정 없이도 다양한 클래스에 적용할 수 있다.

프록시 팩토리 빈 방식의 장점

앞의 데코레이터 패턴이 적용된 프록시를 사용 했을 때의 문제점을 프록피 팩토리 빈은 해결해준다.

  1. 프록시를 적용할 대상이 구현하고 있는 인터페이스를 구현하는 프록시 클래스를 일일이 만들어야 하는 점.
  2. 부가적인 기능이 여러 메소드에 반복적으로 나타나게 돼서 코드 중복의 문제가 발생하는 점 >> 핸들러 메소드 구현

프록시 팩토리 빈의 한계

한 번에 여러 개의 클래스에 공통적인 부가기능을 제공하는 일은 불가능하다. 비슷한 프록시 팩토리 빈의 설정이 중복되는 것을 막을 수 없다.
또한 하나의 타깃에 여러 개의 부가기능을 적용하려 할 때도 문제가 나타난다.
그리고 TransactionHandler 오브젝트가 프록시 팩토리 빈 개수만큼 만들어진다.

스프링은 이 문제점을 또 해결해 줄 수 있는 가능성을 가지고 있지 않은가~